﻿using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TI_BLE_HCI_ClientLib.Controllers;
using TI_BLE_HCI_ClientLib.DataEntities.Advertising;
using TI_BLE_HCI_ClientLib.HCICommands;

namespace TI_BLE_HCI_ClientLib.Tests
{
    [TestFixture]
    public class HCISimpleSerialCentralProfileControllerTester
    {
        [Test]
        public void TestInit()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");
        }

        [Test]
        public void TestConnectTo()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));
        }

        [Test]
        public void TestCloseConnection()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));

            Thread.Sleep(2000);

            Assert.IsTrue(controller.CloseConnection(connHandle));
        }

        [Test]
        public void TestWriteCharacteristicValue()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));

            Assert.IsTrue(controller.WriteCharacteristic(connHandle, 0x0028, 1));

            Thread.Sleep(3000);

            Assert.IsTrue(controller.WriteCharacteristic(connHandle, 0x0028, 2));

            Thread.Sleep(3000);

            Assert.IsTrue(controller.WriteCharacteristic(connHandle, 0x0028, 0));

            Assert.IsTrue(controller.CloseConnection(connHandle));
        }

        [Test]
        public void TestReadCharacteristicValue()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));

            byte value;

            Assert.IsTrue(controller.ReadCharacteristicAsUInt8(connHandle, 0x002f, out value));

            Assert.Greater(value, 0);

            Assert.IsTrue(controller.CloseConnection(connHandle));
        }

        [Test]
        public void TestReadCharacteristicValueAsString()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));

            string value;

            Assert.IsTrue(controller.ReadCharacteristicAsString(connHandle, 0x0003, out value));

            Assert.AreEqual("TI BLE Keyfob", value);

            Assert.IsTrue(controller.CloseConnection(connHandle));
        }

        [Test]
        public void TestNotification()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandle;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandle));

            Assert.IsTrue(controller.WriteCharacteristic(connHandle, 0x0048, (ushort)1));

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnNotification += ((notification) => { if (notification.ValueAsUInt8 == 0x03) are.Set(); });

            are.WaitOne();

            Assert.IsTrue(controller.WriteCharacteristic(connHandle, 0x0048, (ushort)0));

            Assert.IsTrue(controller.CloseConnection(connHandle));
        }

        [Test]
        public void TestYeelight()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM4", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandleYeelight;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("00:18:30:EC:D7:6E"), out connHandleYeelight));

            Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight, 0x0025, "255,0,0,100,,,,,,,"));
            Thread.Sleep(3000);
            Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight, 0x0025, "0,255,0,100,,,,,,,"));
            Thread.Sleep(3000);
            Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight, 0x0025, "0,0,255,100,,,,,,,"));

            Assert.IsTrue(controller.CloseConnection(connHandleYeelight));
        }

        [Test]
        public void TestMultipleYeelightsRobustness()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM5", 115200);

            controller.Start();

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandleYeelight1, connHandleYeelight2, connHandleYeelight3;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("00:18:30:EC:D5:90"), out connHandleYeelight1));
            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("00:18:30:EC:DC:14"), out connHandleYeelight2));
            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("00:18:30:EC:D7:6E"), out connHandleYeelight3));

            for (int i = 0; i < 1000; i++)
            {
                Trace.TraceInformation("Iteration #{0}...", i + 1);

                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight1, 0x0025, "255,0,0,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight2, 0x0025, "255,0,0,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight3, 0x0025, "255,0,0,100,,,,,,,"));
                
                Thread.Sleep(1000);
                
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight1, 0x0025, "0,255,0,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight2, 0x0025, "0,255,0,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight3, 0x0025, "0,255,0,100,,,,,,,"));
                Thread.Sleep(1000);
                
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight1, 0x0025, "0,0,255,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight2, 0x0025, "0,0,255,100,,,,,,,"));
                Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight3, 0x0025, "0,0,255,100,,,,,,,"));
                Thread.Sleep(1000); 
            }

            Assert.IsTrue(controller.CloseConnection(connHandleYeelight1));
            Assert.IsTrue(controller.CloseConnection(connHandleYeelight2));
            Assert.IsTrue(controller.CloseConnection(connHandleYeelight3));
        }


        [Test]
        public void TestYeelightAndKeyfob()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandleKeyfob;
            ushort connHandleYeelight;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandleKeyfob));
            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("00:18:30:EC:D5:90"), out connHandleYeelight));

            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0048, (ushort)1));

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnNotification += ((notification) =>
            {
                switch (notification.ValueAsUInt8)
                {
                    case 0x01:
                        Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight, 0x0025, "255,0,0,100,,,,,,,"));
                        break;

                    case 0x02:
                        Assert.IsTrue(controller.WriteCharacteristic(connHandleYeelight, 0x0025, "0,255,0,100,,,,,,,"));
                        break;

                    case 0x03:
                        are.Set();
                        break;
                }
            });

            are.WaitOne();

            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0048, (ushort)0));

            Assert.IsTrue(controller.CloseConnection(connHandleKeyfob));
            Assert.IsTrue(controller.CloseConnection(connHandleYeelight));
        }

        [Test]
        public void TestSensorTagAndKeyfob()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandleKeyfob;
            ushort connHandleSensorTag;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandleKeyfob));
            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:76:29"), out connHandleSensorTag));

            Assert.IsTrue(controller.WriteCharacteristic(connHandleSensorTag, 0x006c, (ushort)1));

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnNotification += ((notification) =>
            {
                if (notification.ConnHandle == connHandleSensorTag)
                {
                    switch (notification.ValueAsUInt8)
                    {
                        case 0x00:
                            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0028, (byte)0));
                            break;

                        case 0x01:
                            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0028, (byte)1));
                            break;

                        case 0x02:
                            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0028, (byte)2));
                            break;

                        case 0x03:
                            are.Set();
                            break;
                    }
                }
            });

            are.WaitOne();

            Assert.IsTrue(controller.WriteCharacteristic(connHandleSensorTag, 0x006c, (ushort)0));

            Assert.IsTrue(controller.CloseConnection(connHandleKeyfob));
            Assert.IsTrue(controller.CloseConnection(connHandleSensorTag));
        }

        [Test]
        public void TestKeyfobTwoFunctions()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM3", 115200);

            Assert.IsTrue(controller.Init(), "Init() Failed");

            ushort connHandleKeyfob;

            Assert.IsTrue(controller.ConnectTo(TestHelpers.StringWithColonsToByteArrayReversed("90:59:AF:0B:7D:96"), out connHandleKeyfob));

            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0048, (ushort)1));

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnNotification += ((notification) =>
            {
                if (notification.ValueAsUInt8 == 3)
                {
                    are.Set();
                }
                else
                {
                    controller.WriteCharacteristic(connHandleKeyfob, 0x0028, notification.ValueAsUInt8);
                }

            });

            are.WaitOne();

            Assert.IsTrue(controller.WriteCharacteristic(connHandleKeyfob, 0x0048, (ushort)0));

            Assert.IsTrue(controller.CloseConnection(connHandleKeyfob));
        }

        [Test]
        public void TestScanDevices()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM12", 115200);

            controller.Start();

            Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            controller.OnDeviceInformation += ((evt) =>
                {
                    Trace.TraceInformation("Device found : {0}", evt.Addr.ToHexString());

                    foreach(var ad in evt.AdvertisingAndScanResponseData.AdvertisingDataStructures)
                    {
                        if(ad is FlagsAdvertisingDataStructure)
                        {
                            var adFlags=ad as FlagsAdvertisingDataStructure;

                            Trace.TraceInformation("\tFlags: {0}", adFlags.ValueBitField.ToString());

                            continue;
                        }

                        if (ad is LocalNameAdvertisingDataStructure)
                        {
                            var adName = ad as LocalNameAdvertisingDataStructure;

                            Trace.TraceInformation("\tLocal Name: {0}", adName.DeviceName);

                            continue;
                        }

                        if (ad is TxPowerLevelAdvertisingDataStructure)
                        {
                            var adTxPower = ad as TxPowerLevelAdvertisingDataStructure;

                            Trace.TraceInformation("\tTx Power Level: {0}", adTxPower.TxPowerLevel);

                            continue;
                        }

                        if (ad is ManufacturerSpecificDataAdvertisingDataStructure)
                        {
                            var adManufacturerSpecificData = ad as ManufacturerSpecificDataAdvertisingDataStructure;

                            Trace.TraceInformation("\tManufacturer Specific Data: {0}", adManufacturerSpecificData.Data.ToHexString());

                            continue;
                        }

                        Trace.TraceWarning("Unknown Advertising Data Structure ! : {0}",ad.Data.ToHexString());
                    }
                });

            Assert.IsTrue(controller.BeginDeviceDiscovery());

            Thread.Sleep(1000);

            AutoResetEvent are = new AutoResetEvent(false);

            controller.OnDeviceDiscoveryDone += ((evt) =>
                {
                    are.Set();
                });

            Assert.IsTrue(controller.EndDeviceDiscovery());

            Assert.IsTrue(are.WaitOne(1000));
        }

        [Test]
        public void TestSetGAPParamAndScanDevices()
        {
            HCISimpleSerialCentralProfileController controller = new HCISimpleSerialCentralProfileController("COM5", 115200);
            AutoResetEvent are = new AutoResetEvent(false);
            Stopwatch sw = new Stopwatch();

            controller.Start();

            //Assert.IsTrue(controller.Reset(HCIController.ResetTypeEnum.HardReset), "Reset() Failed");

            Assert.IsTrue(controller.Init(), "Init() Failed");

            Assert.IsTrue(controller.SetGAPParameter(HCIController.ParamIDEnum.TGAP_GEN_DISC_SCAN, 3000));

            sw.Start();

            Assert.IsTrue(controller.BeginDeviceDiscovery());

            controller.OnDeviceDiscoveryDone += ((evt) =>
            {
                are.Set();
            });

            Assert.IsTrue(are.WaitOne(15000));

            sw.Stop();

            Assert.Less(sw.ElapsedMilliseconds,5000);
        }
    }
}
